﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.Net.Sockets;
using System.Net;



namespace Anaida
{
    public class WebSocketAdapter {

	    private IWebSocketListener listener = null;
	    private String host="localhost";
	    private int port=8080;
	    private String path="/websocket";
	    private HTTPRequestParser parser = new HTTPRequestParser();
	    private Socket socket = null;
        private byte[] buffer = new byte[8192];

	    public const byte WS_PACKET_START_BYTE = (byte)0x00;
	    public const byte WS_PACKET_END_BYTE = (byte) 0xff;
	    public byte[] separator = new byte[]{13,10};
	    
	
	    public const int START_STATE = 0;
	    public const int PARSED_GET_STATE = 1;
	    public const int PARSED_HEADERS = 2;
	
	    private int state = START_STATE;
        private bool readingStarted = false;
	
	    public WebSocketAdapter(String host, int port, String path, IWebSocketListener listener){
		    this.path = path;
		    this.port = port;
		    this.host = host;
		    this.listener = listener;

            IPHostEntry lipa = Dns.Resolve(host);
	        IPEndPoint lep = new IPEndPoint(lipa.AddressList[0], port);
            socket = new Socket(lep.Address.AddressFamily, SocketType.Stream, ProtocolType.IP);
            socket.BeginConnect(lep, new AsyncCallback(ConnectCallback), socket );
	    }

        private byte[] AppendToBuffer(byte[] buffer, byte[] part)
        {
            int length = buffer.Length + part.Length;
            byte[] result = new byte[length];
            System.Array.Copy(buffer, 0, result, 0, buffer.Length);
            System.Array.Copy(part, 0, result, buffer.Length, part.Length);
            return result;
        }

        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket client = (Socket)ar.AsyncState;
                // Complete the connection.
                client.EndConnect(ar);
                //Console.WriteLine("Socket connected to {0}", client.RemoteEndPoint.ToString());


                byte[] buf = new byte[0];

                buf = AppendToBuffer(buf, Encoding.ASCII.GetBytes("GET /websocket HTTP/1.1"));
                buf = AppendToBuffer(buf, separator);
                buf = AppendToBuffer(buf, Encoding.ASCII.GetBytes("Upgrade: WebSocket"));
                buf = AppendToBuffer(buf, separator);
                buf = AppendToBuffer(buf, Encoding.ASCII.GetBytes("Connection: Upgrade"));
                buf = AppendToBuffer(buf, separator);
                buf = AppendToBuffer(buf, Encoding.ASCII.GetBytes("Host: "+host+":"+port.ToString()));
                buf = AppendToBuffer(buf, separator);
                buf = AppendToBuffer(buf, Encoding.ASCII.GetBytes("Origin: http://"+host+":"+port.ToString()));
                buf = AppendToBuffer(buf, separator);
                buf = AppendToBuffer(buf, Encoding.ASCII.GetBytes("Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5"));
                buf = AppendToBuffer(buf, separator);
                buf = AppendToBuffer(buf, Encoding.ASCII.GetBytes("Sec-WebSocket-Key2: 4 @2  43485xW%0l 2 3"));
                buf = AppendToBuffer(buf, separator);
                buf = AppendToBuffer(buf, separator);
                buf = AppendToBuffer(buf, new byte[] { 23, 45, 34, 56, 45, 56, 56, 67 });
                
                Send(buf, false);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
        private void ReceiveCallback( IAsyncResult ar ) {
            Socket client = (Socket)ar.AsyncState;
            try
            {
                // recieve handshake
                int numRead = client.EndReceive(ar);
                if (numRead == -1)
                {
                    Console.Out.WriteLine("Read -1");
                    CloseAdapter();
                    return;
                }

                if (numRead >= 0)
                {
                    byte[] message = new byte[numRead];
                    System.Array.Copy(buffer, 0, message, 0, numRead);
                    parser.AddToBuffer(message);

                    byte[] line = null;
                    while ((line = parser.getALine()) != null)
                    {

                        switch (state)
                        {
                            case START_STATE:
                                state = PARSED_GET_STATE;
                                break;
                            case PARSED_GET_STATE:
                                ProcessHeader(line);
                                break;
                            case PARSED_HEADERS:
                                HandleWebSocketFrames(line);
                                break;
                        }
                    }
                }


            }
            catch (Exception e)
            {
                CloseAdapter();
            }
            finally
            {
                try
                {
                    client.BeginReceive(buffer, 0, buffer.Length, 0, new AsyncCallback(ReceiveCallback), client);
                }
                catch (Exception f) { }
            }
        }

	    private void ProcessHeader(byte[] line){
		    if (line.Length ==0){
			    state = PARSED_HEADERS;
			    parser.GetNBytes(16,new byte[0]);
			    parser.SetSeparator(new byte[]{WS_PACKET_END_BYTE});
			    if (listener != null) listener.Onopen();
		    }
	    }
	
	    private void HandleWebSocketFrames(byte[] packet){
    	
		
    	    byte[] rawpacket = new byte[packet.Length-1];
    	    System.Array.Copy(packet,1,rawpacket,0,rawpacket.Length);
    	
    	    if (listener != null) listener.Onmessage(rawpacket);
        }
	
	    private void CloseAdapter() {
		    if (this.listener != null) this.listener.Onclose();
		    try {
			    socket.Close();
		    } catch (Exception f){}
	    }
	
	    public void Send(byte[] message, bool wrap){
		    try {
                if (wrap)
                {
                    byte[] payload = new byte[message.Length + 2];
                    payload[0] = WS_PACKET_START_BYTE;
                    System.Array.Copy(message, 0, payload, 1, message.Length);
                    payload[payload.Length - 1] = WS_PACKET_END_BYTE;
                    // Begin sending the data to the remote device.
                    socket.BeginSend(payload, 0, payload.Length, SocketFlags.None,
                        new AsyncCallback(SendCallback), socket);
                }
                else
                {
                    socket.BeginSend(message, 0, message.Length, SocketFlags.None,
                        new AsyncCallback(SendCallback), socket);
                }
		    }catch (Exception e){
			    CloseAdapter();
			    throw e;
		    }
	    }


        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                Socket client = (Socket)ar.AsyncState;
                // Complete sending the data to the remote device.
                int bytesSent = client.EndSend(ar);
                
                //Console.WriteLine("Sent {0} bytes to server.", bytesSent);
                if (!readingStarted)
                {
                    client.BeginReceive(buffer, 0, buffer.Length, 0, new AsyncCallback(ReceiveCallback), client);
                    readingStarted = true;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    }

}
